home *** CD-ROM | disk | FTP | other *** search
/ Disc to the Future 2 / Disc to the Future Part II Programmer's Reference (Wayzata Technology)(6013)(1992).bin / MAC / THINKC / 4_0 / TUTORIAL / TUTORIAL.C
C/C++ Source or Header  |  1990-09-10  |  38KB  |  1,237 lines

  1. /*
  2.  
  3. ==============================================================================
  4.  
  5.                       WormMucking Screen Saver Tutorial
  6.  
  7.                                by Michael Pye
  8.  
  9.                             For use with THINK C
  10.  
  11. ==============================================================================
  12.  
  13. The "WormMucking Tutorial" is a modification of the "Sunset Tutorial" I 
  14. wrote for the QuickTools/Sunset package - yet another screen saver soon to
  15. be released.  (The Sunset screen saver works very much like the After Dark 
  16. or Pyro 4.0 screen savers.  There is an INIT portion, which handles all 
  17. interfacing to the Mac, and there are the individual screen saver modules.
  18. The individual modules are loaded and called by the INIT and handle all of 
  19. the animation.)  It was intended to be a tutorial on how to write your own 
  20. screen saver for use with Sunset.  This version is modified to run as a 
  21. stand-alone application.  The original version is a code resource that gets
  22. loaded and called from Sunset.  
  23.  
  24. The main difference is with the main() routine.  Since Sunset isn't around
  25. to load and run the screen saver, the main() routine was made to emulate
  26. what Sunset does.  That is, you can pretend that the main() routine is the
  27. Sunset INIT that calls the WormMucking screen saver.  All the rest of the code
  28. besides main() is the WormMucking saver.
  29.  
  30. I go into detail about Sunset here and how it works because the tutorial talks
  31. extensively about it.  So in order to not have to re-write the whole tutorial
  32. (terminally lazy), you can just pretend that whenever "Sunset" is mentioned,
  33. for our sakes it means the "main()" routine.  (Normally the main() routine 
  34. of the module would be the same routine that is named MySaver() here.)  
  35. The MySaver() routine is the MAIN routine of WormMucking.
  36.  
  37. Now that I have thouroughly screwed up this explanation and have one giant 
  38. headache, I'll shut up and get on with the good stuff.......
  39.  
  40.  
  41.  
  42. Writing a screen saver module is a great way to get familiar with programming
  43. on the Macintosh.  Almost all the user interface aspects are non-existant,
  44. all you have to do is supply the animation routines.  Additionally, there 
  45. are no "guidelines" you have to adhere to.  Your only concerns are to make 
  46. sure your module is stable (doesn't crash) and that the animation is pleasing 
  47. to you.
  48.  
  49. The tutorial comes in two files:
  50.  
  51. 1 -    Tutorial.c      -- This is the file you are reading right now.
  52.     It is both a tutorial on how to write a Sunset screen saver, and
  53.     source code to an actual working module.
  54.     
  55. 2 -    Tutorial.╣.rsrc -- This is the resource file that Tutorial.c
  56.     needs to complete the screen saver.  It contains all the dialogs boxes,
  57.     version numbers, screen saver modifiers, etc.  At the end of 
  58.     Tutorial.c is a discussion of all the resources in this file.
  59.  
  60.  
  61. When Sunset calls your module, it expects you to take one of four actions.
  62. The requested action is passed to your module's main() routine as an integer.
  63. The four actions are, in order of appearance: INIT, RUN, END, and CONFIG.
  64.  
  65. 1  -    INIT   -- Your screen saver is called with this message when Sunset
  66.         initially starts.  This is the first message Sunset will send to the
  67.         saver and it will only be sent once.  When your saver receives this 
  68.         message, the current GrafPort is set to a window that spans all
  69.         connected monitors and all screens have been blanked out.  You should
  70.         do all the initialization the saver requires at this time.  Typical 
  71.         things done at this time are:  allocating storage, determining the 
  72.         dimensions of the screen, checking to see if the MacPortable is 
  73.         being used, and checking to see if Color Quickdraw is available.
  74.         Additionally, you may want your saver to do some initial animation.
  75.  
  76. 2    -    RUN     -- This is where the majority of your saver's time will be 
  77.         spent.  Sunset will call your saver with this message over and over 
  78.         until Sunset deactivates.  Each time your saver is called with this 
  79.         message, it should animate a single frame of animation.  You should
  80.         try to avoid doing extremely complex tasks that require a significant
  81.         amount of time.  That way, the person using your saver won't have to 
  82.         wait to regain control of their Macintosh.
  83.  
  84. 3    -    END     -- When Sunset calls your saver with the END message, the 
  85.         user has awakened the screen and Sunset is telling you it's time 
  86.         to wrap it up.  When your saver receives this message it should 
  87.         perform any final actions/animation and then dispose of any storage
  88.         that was allocated.  
  89.  
  90. 4    -    CONFIG -- This message is sent to your saver only when the person
  91.         using Sunset has clicked on the "Configure..." button in the Control
  92.         Panel cdev.  This message is different from the previous three in 
  93.         the following ways:  
  94.  
  95.         *    The screen is not blanked out. 
  96.         *    The CONFIG message will not be preceded by the INIT message.  
  97.  
  98.         Your saver should present a dialog box to the user when it receives 
  99.         a CONFIG message.  If it is customizable, it should provide the 
  100.         customize options here.  If the saver isn't customizable, a simple
  101.         dialog box presenting the name of the saver will do fine.
  102.  
  103. Handling each of the messages that Sunset passes to your saver will be 
  104. discussed in more detail as they come up in the source code.
  105.  
  106.  
  107. Below is the source code to the WormMucking Sunset module.  Above each 
  108. function is a description of what the function does in the order it
  109. is done.  Hopefully by examining the source code to a working module,
  110. it will help you in making screen savers of your own.
  111.  
  112.  
  113. */
  114.  
  115. /******************************************************************\
  116.  
  117.                           #includes & #defines
  118.         
  119. \******************************************************************/
  120.  
  121. #include    "ColorToolbox.h"   /* used for color worms               */
  122.  
  123. #define cBaseResID        1000   /* base resource file id#             */
  124. #define cMax            32     /* max number & length of worms       */
  125. #define cNilPointer        0L     /* nil */
  126. #define cMoveToFront    -1L    /* brings config dialog to front      */
  127. #define OR                ||     /* lazy */
  128. #define AND                &&     /*  ""  */
  129. #define cNegative        0      /* used for worms movement            */
  130. #define cPositive        1
  131.  
  132. #define cOK                1      /* items 1-6 in the config dialog     */
  133. #define cCancel            2      /* plus the UserItem that surrounds   */
  134. #define cwormTally        3      /* the OK button.                     */
  135. #define cWormLength        4
  136. #define cMultiColored    5
  137. #define cWormLinks        6
  138. #define cUserItem        12
  139.  
  140. #define    cTallyNum        cBaseResID     /* id#s of configurable parts */
  141. #define cLengthNum        cBaseResID+1
  142. #define    cMultiNum        cBaseResID+3
  143. #define cLinksNum        cBaseResID+4
  144.  
  145. #define INIT            1      /* the four messages that Sunset will */
  146. #define RUN                2      /* pass to the saver                  */
  147. #define END                3
  148. #define CONFIG            4
  149.  
  150.  
  151. typedef    int    * intPtr, ** intH; /* used to get configurable resources */
  152.  
  153. /*
  154.     Define our global variables
  155. */
  156.  
  157. Boolean        gMemoryOK, gColorQD, gMultiColored, gWormLinks;
  158. int            gWormTally, gWormLength;
  159. int            gV[cMax], gH[cMax];
  160. int            gMoveMax;
  161. int            gScreenDepth;
  162. Rect        gWormTrail[cMax][cMax], gWormRect[cMax];
  163. Rect        gScreenRect;
  164. Point        gIncentive[cMax];
  165. GrafPtr        gViewScreen;
  166. RGBColor    gWormColor, gSpawnColor[cMax];
  167.     
  168.  
  169.     
  170. /***************************** main *******************************\
  171.  
  172.     This is the main() routine of the application.  This routine
  173.     first initializes the toolbox managers.  It then calls the 
  174.     WormMucking module, through the MySaver() routine, to bring
  175.     up the configure dialog box.
  176.     
  177.     After OK or Cancel are clicked, it sets up a window that spans
  178.     all the monitors.  It then calls WormMucking, again through
  179.     the MySaver() routine, to Initialize the worms and screens.
  180.     
  181.     Then it enters a loop and continually calls WormMucking, 
  182.     through MySaver(), with a RUN message.  WormMucking will 
  183.     animate a single frame of animation every time through the 
  184.     loop.  When a key is pressed, or the mouse is clicked, it
  185.     exits the loop and calls WormMucking with an END message.
  186.     
  187.     It then gets rid of the window covering all monitors and
  188.     sets everything back to normal before ending.
  189.             
  190. \******************************************************************/
  191.  
  192. main()
  193. {
  194.     Boolean            done=false, go=true;
  195.     WindowPtr        w;
  196.     RgnHandle        argn;
  197.     Str255            s;
  198.     GrafPtr            oldPort;
  199.     Rect            r;
  200.     SysEnvRec        world;
  201.     OSErr            err;
  202.     Point            mypt;
  203.     EventRecord        evt;
  204.  
  205. /* Initialize the toolbox managers */
  206.  
  207.     MaxApplZone();
  208.     InitGraf(&thePort);
  209.     InitCursor();
  210.     InitFonts();
  211.     InitWindows();
  212.     TEInit();
  213.     InitDialogs(0L);
  214.     InitMenus();
  215.     FlushEvents(everyEvent, 0);
  216.  
  217. /* Bring up the configure dialog and allow changes */
  218.  
  219.     MySaver(CONFIG);
  220.  
  221. /* Get things set up for the WormMucking module */
  222.                                 
  223.     HideCursor();
  224.     argn=NewRgn();
  225.     s[0]=0;
  226.  
  227. /* Make the window span every monitor and black out the menu bar too */
  228.  
  229.     GetPort(&oldPort);
  230.     r=(**GrayRgn).rgnBBox;
  231.     if (r.top>0)
  232.         r.top=0;
  233.                                 
  234.     err=SysEnvirons(1,&world);
  235.     if (world.hasColorQD)
  236.         w=NewCWindow(0L,&r,&s,true,2,-1L,false,0);
  237.     else
  238.         w=NewWindow(0L,&r,&s,true,2,-1L,false,0);
  239.  
  240. /* Do this so the strip doesn't pop down in the middle of our window */
  241.  
  242.     SetWRefCon(w,'worm');
  243.     RectRgn(argn,&w->portRect);
  244.     UnionRgn(argn,w->visRgn,w->visRgn);
  245.     DisposeRgn(argn);
  246.                                             
  247.     SetPort(w);
  248.     SetPt(&mypt,0,0);
  249.     GlobalToLocal(&mypt);
  250.     SetOrigin(-mypt.h,-mypt.v);
  251.     ClipRect(&w->portRect);
  252.  
  253. /* Call the screen saver with the INIT message */
  254.                                 
  255.     MySaver(INIT);
  256.                                 
  257. /* Now loop through and call the saver with the RUN message until the user does something */
  258.  
  259.     FlushEvents(everyEvent,0);
  260.     ValidRect(&w->portRect);
  261.                                                                 
  262.     while (go)
  263.         {
  264.         SystemTask();
  265.         MySaver(RUN);
  266.         GetNextEvent(everyEvent,&evt);
  267.         if ((evt.what==mouseDown) || (evt.what==keyDown))
  268.             go=false;
  269.         }
  270.                                     
  271. /* We're done, call the screen saver with the END message and that's that! */
  272.  
  273.     MySaver(END);
  274.  
  275.     FlushEvents(everyEvent,0);
  276.     DrawMenuBar();
  277.     DisposeWindow(w);
  278.     SetPort(oldPort);
  279.     InitCursor();
  280.  
  281. }/*    main */
  282.  
  283.  
  284. /**************************** MySaver *****************************\
  285.  
  286.     MySaver() uses a switch statement to divide into four areas, 
  287.     one for each possible value that might get passed to it:
  288.     
  289.     INIT:    InitializeSaver() is called to initialize all
  290.             variables and to draw the initial worms.
  291.             
  292.     RUN:    RUN first calls MoveWorms() to animate a single
  293.             frame of animation by moving the worms one link.
  294.             It then erases the last link in the chain and 
  295.             adjusts each link's logical position.
  296.             
  297.     END:    END removes all events from the queue, disposes
  298.             of any storage, and sets the Pen back to normal.  
  299.         
  300.     CONFIG: CONFIG simply calls the ConfigureSaver() routine
  301.             to handle the customization options.
  302.             
  303. \******************************************************************/
  304.  
  305. MySaver(message)
  306. int        message;
  307.  
  308. {
  309. int        i, ii;
  310.  
  311. switch (message)
  312.     {
  313.     case INIT:
  314.         InitializeSaver();
  315.         break;
  316.             
  317.     case RUN:
  318.         MoveWorms();
  319.         for ( ii = 0; ii <= gWormTally-1; ii++ )
  320.             {
  321.             ForeColor(blackColor);
  322.             if ( gWormLinks )
  323.                 FrameOval( &gWormTrail[ii][gWormLength] );
  324.             else
  325.                 PaintOval( &gWormTrail[ii][gWormLength] );
  326.                 
  327.             for ( i = gWormLength; i > 0; i-- )
  328.                 gWormTrail[ii][i] = gWormTrail[ii][i-1];
  329.             gWormTrail[ii][0] = gWormRect[ii];
  330.             }
  331.         break;
  332.         
  333.     case END:
  334.         FlushEvents( everyEvent,0 );
  335.         PenSize(1,1);
  336.         PenNormal();
  337.         break;
  338.  
  339.     case CONFIG:
  340.         ConfigureSaver();
  341.         break;
  342.             
  343.     }
  344.     
  345. }/* MySaver */
  346.  
  347.  
  348. /*********************** InitializeSaver **************************\
  349.  
  350.     InitializeSaver() starts by determining what environment 
  351.     the saver is being run under.  This is done by calling the
  352.     InitializeScreens() routine.
  353.     
  354.     The resource file is then read in to get the customized
  355.     options.  Before the resource file is checked however, all
  356.     options are given default values.  This is in case the 
  357.     resource file has been corrupted and has either faulty
  358.     or no data in it.
  359.     
  360.     Finally, the worm's colors are calculated (if appropriate),
  361.     initial values and starting points are set up, and the
  362.     worms are drawn on the screen.
  363.     
  364. \******************************************************************/
  365.  
  366. InitializeSaver()
  367. {
  368. StringHandle    tempHandle;
  369. long            temp;
  370. int                width, height, i;
  371. intH            modifier;
  372. GrafPtr            oldPort;
  373. SysEnvRec        theWorld;
  374. GDHandle        maxDevice, oldDevice;
  375. GrafPtr            viewScreen;
  376. CGrafPtr        workArea;
  377. CGrafPort        workPort;
  378.     
  379.  
  380. /*
  381.      Call the InitializeScreens() routine to check to see whether 
  382.      Color Quickdraw is available and to get information on the 
  383.      display characteristics of all connected monitors.  See the 
  384.      InitializeScreens() routine for details.
  385. */
  386.  
  387. InitializeScreens();
  388.  
  389. /*
  390.     Set the default values for the customized variables.  Then
  391.     read in the resource file to get these values.
  392. */
  393.  
  394. gWormTally = 15;
  395. gWormLength = 15;
  396. gMultiColored = 0;
  397. gWormLinks = 0;
  398.     
  399. modifier = (intH)GetResource( 'mods', cTallyNum );
  400. temp = (**modifier);
  401. if ( temp >= 1 AND temp < cMax-1 )
  402.     gWormTally = temp;
  403. modifier = (intH)GetResource( 'mods', cLengthNum );
  404. temp = (**modifier);
  405. if ( temp >= 1 AND temp < cMax-1 )
  406.     gWormLength = temp-1;
  407. modifier = (intH)GetResource( 'mods', cMultiNum );
  408. temp = (**modifier);
  409. if ( temp >= 0 AND temp <= 1 )
  410.     gMultiColored = temp;
  411. modifier = (intH)GetResource( 'mods', cLinksNum );
  412. temp = (**modifier);
  413. if ( temp >= 0 AND temp <= 1 )
  414.     gWormLinks = temp;
  415.       
  416.  
  417. /*
  418.     Initialize the colors of each worm and set the "incentive"
  419.     for the worms to be nil.  Then, calculate the initial 
  420.     position for the worms and go through a loop to draw them 
  421.     on the screen.
  422. */
  423.  
  424. gMoveMax = 3;
  425. if ( gColorQD AND gScreenDepth > 3 )
  426.       for ( temp = 0; temp <= gWormTally-1; temp++ )
  427.           {
  428.           CalcRGBColor();
  429.           gSpawnColor[temp] = gWormColor;
  430.           }
  431. for ( temp = 0; temp <= gWormTally-1; temp++ )
  432.     {
  433.     gIncentive[temp].h = 0;
  434.     gIncentive[temp].v = 0;
  435.     }
  436. InitializeWorms();
  437. for ( temp = gWormLength; temp >= 0; temp-- )
  438.     {
  439.     MoveWorms();
  440.     for ( i=0; i <= gWormTally-1; i++ )
  441.         {
  442.         gWormTrail[i][temp] = gWormRect[i];
  443.         gWormTrail[i][0] = gWormRect[i];
  444.         }
  445.     }
  446.         
  447. }/* InitializeSaver */
  448.  
  449.  
  450. /*********************** InitializeScreens ************************\
  451.  
  452.     InitializeScreens() does the following (in order):
  453.     
  454.     * Checks to see whether Color Quickdraw is available.
  455.     
  456.     * If Color Quickdraw is available, it scans all 
  457.       connected devices to see what the pixel depth of each 
  458.       device is.  The sole purpose for this is for multiple 
  459.       monitor setups where one monitor is set for multi-bit
  460.       color and another monitor is set for single bit B&W.
  461.  
  462.     * The Rect that surrounds all connected monitors is then
  463.       retrieved and stored in the global gScreenRect.
  464.       All drawing will normally be confined to this Rect.
  465.       Actually, the worms are allowed to extend past these
  466.       boundaries by about 4 pixels in each direction.  This 
  467.       allows the worms appear to move more smoothly when they 
  468.       encounter the edge of the screen.
  469.       
  470.     * A check is then made to see whether a multiple monitor
  471.       setup is being used.  If so, and if one of the monitors
  472.       is set for one bit B&W while another is set for multi-bit
  473.       color or grey scale, a new Rect will be stored in the 
  474.       global gScreenRect.  This will restrict the drawing
  475.       to the the screen that can display the most colors.  This
  476.       Rect is scanned for at the same time as the depths of the 
  477.       monitors is checked.  
  478.       
  479.     * Next, a check is made to see whether the Mac Portable is 
  480.       being used and a flag is set if so.  This is done by 
  481.       checking to see if a pixel on the screen is turned on or
  482.       off.  The Mac Portable's screen is cleared to white in 
  483.       order to save the screen, unlike the usual black.  The 
  484.       Sunset INIT handles clearing the screen before the saver
  485.       module is called.  So if the pixel is white, the portable
  486.       is being used.  (Any pixel on the screen can be used
  487.       as the check, but there are two things to remember.  One,
  488.       not all screens are the same size so the pixel checked 
  489.       cannot be very large.  Two, if you use the debugger in
  490.       Think C, you'll want to check a pixel that isn't on the 
  491.       Menu Bar - otherwise, while debugging, the module may 
  492.       think that the portable is being used, when in fact, it 
  493.       isn't.  Checking the pixel at 100,100 is a good way to 
  494.       get around both of the above problems.)
  495.       
  496.     * PenNormal() is then called to make sure that the drawing
  497.       window is set up correctly.  This is done because Sunset
  498.       allows for different modules to be called between the time 
  499.       the Macintosh is "put to sleep" and the time it is 
  500.       "awakened".  We need to make sure that a previous module 
  501.       has not altered the attributes of the window.
  502.       
  503.     * Last, the ForeColor & BackColor are set properly for the 
  504.       same reasons as above.  Then, all screens are cleared to 
  505.       black.
  506.       
  507.     * Note:  The Sunset INIT handled whether the MacPortable was
  508.       being used.  All references to the the MacPortable were 
  509.       taken out of the application version of WormMucking.
  510.         
  511. \******************************************************************/
  512.  
  513. InitializeScreens()
  514. {
  515. GrafPtr            oldPort;
  516. SysEnvRec        theWorld;
  517. GDHandle        gDevice, oldDevice;
  518. GrafPtr            viewScreen;
  519. CGrafPtr        workArea;
  520. CGrafPort        workPort;
  521. Boolean            active, screen;
  522. int                depth, gDepth=0, minDepth=1024, maxDepth=1;
  523. Rect            gRect;
  524.     
  525.     
  526. GetPort( &oldPort );
  527. if ( SysEnvirons( 1, &theWorld ) == envNotPresent )
  528.     gColorQD = false;
  529. else
  530.     gColorQD = (theWorld.hasColorQD);
  531.  
  532. if ( gColorQD )
  533.     {
  534.     oldDevice = GetGDevice();
  535.     gDevice = GetDeviceList();
  536.     while (gDevice != cNilPointer)
  537.         {
  538.         screen = TestDeviceAttribute(gDevice, screenDevice);
  539.         active = TestDeviceAttribute(gDevice, screenActive);
  540.         if (screen AND active)
  541.             {
  542.             SetGDevice(gDevice);
  543.             workArea = &workPort;
  544.             OpenCPort( workArea );
  545.             depth = (**(*workArea).portPixMap).pixelSize;
  546.             if ( depth < minDepth )
  547.                 minDepth = depth;
  548.             if ( depth > maxDepth )
  549.                 maxDepth = depth;
  550.             if ( depth > gDepth )
  551.                 {    
  552.                 gRect = (**gDevice).gdRect;
  553.                 gDepth = depth;
  554.                 }
  555.             CloseCPort( workArea );
  556.             }
  557.         gDevice = GetNextDevice(gDevice);
  558.         }
  559.     SetGDevice( oldDevice );
  560.     }
  561. SetPort( oldPort );
  562.  
  563. GetPort( &(gViewScreen) );
  564. gScreenRect = gViewScreen->portRect;
  565. gScreenDepth = maxDepth;
  566. if ( minDepth == 1 AND maxDepth > 1 )
  567.     gScreenRect = gRect;
  568.         
  569. PenNormal();
  570.  
  571. ForeColor( whiteColor );
  572. BackColor( blackColor );
  573. EraseRect( &gScreenRect );
  574.     
  575. }/* InitializeScreens */
  576.  
  577.  
  578. /************************ InitializeWorms *************************\
  579.  
  580.     InitializeWorms() calculates the initial starting position
  581.     for each worm.  All worms start at the bottom of the screen
  582.     and have an equal horizontal distance between them.
  583.         
  584. \******************************************************************/
  585.  
  586. InitializeWorms()
  587. {
  588. int         left, top, right, bottom;
  589. int        i, temp;
  590.  
  591. left = gScreenRect.left;
  592. top = gScreenRect.top;
  593. right = gScreenRect.right;
  594. bottom = gScreenRect.bottom;
  595. temp = (right-left)/gWormTally+1;
  596.     
  597. for ( i = 0; i <= gWormTally; i++ )
  598.     {
  599.     SetRect( &gWormTrail[i][0], temp*i, bottom-4, temp*i+4, bottom );
  600.     gV[i] = -3;
  601.     gH[i] = 0;
  602.     }
  603.     
  604. }/* InitializeWorms */
  605.  
  606.  
  607. /*********************** DrawDefaultItem **************************\
  608.  
  609.     DrawDefaultItem() is used to draw the thick black border around
  610.     the default item in a dialog box.  The default item is usually
  611.     the "OK" button.  There is a UserItem which surrounds the OK
  612.     button in the DITL of the .rsrc file.  ConfigureSaver assigns
  613.     a pointer to the routine (DrawDefaultItem) to that UserItem.
  614.     Then, whenever the dialog manager goes to update the configure
  615.     dialog, it calls this routine to draw the UserItem.  Our 
  616.     UserItem routine then, draws the default button outline using
  617.     the Rect of the UserItem - which surrounds the OK button.
  618.         
  619. \******************************************************************/
  620.  
  621. pascal void DrawDefaultItem( dialog, item )
  622. DialogPtr    dialog;
  623. int            item;
  624. {
  625.     int                itemType;
  626.     Rect            itemRect;
  627.     Handle            itemHandle;
  628.  
  629.     GetDItem( dialog, item, &itemType, &itemHandle, &itemRect );
  630.     PenSize( 3, 3 );
  631.     FrameRoundRect( &itemRect, 16, 16 );
  632.     PenSize( 1, 1 );
  633.     
  634. }/* DrawDefaultItem */
  635.  
  636.  
  637. /************************ ConfigureSaver **************************\
  638.  
  639.     ConfigureSaver() first gets the dialog from the resource 
  640.     file.  The dialog is then centered on the screen (over the 
  641.     Control Panel) with the CenterDialog() routine.
  642.     
  643.     ConfigureSaver() finds whether Color Quickdraw is available 
  644.     and the deepest device used.  It then reads in the customizable 
  645.     options from the resource file.
  646.     
  647.     ConfigureSaver() then uses this information to adjust
  648.     the dialog.  All customized values are set and parts of
  649.     the dialog are adjusted if color quickdraw is not available.
  650.     A pointer to DrawDefaultItem is then assigned to the UserItem
  651.     which surrounds the OK button.  This will allow the default
  652.     button outline to be drawn automatically for us by the 
  653.     dialog manager.
  654.     
  655.     It stays in the loop until the "OK" or "Cancel" buttons 
  656.     are pressed.  The Cancel button does nothing.  OK checks to 
  657.     see whether values given by the user are "legal" and adjusts 
  658.     them if not.  It then writes all values back out to the 
  659.     resource file.
  660.     
  661.     InitializeSaver() will then be able to read these values 
  662.     in and adjust the screen saver accordingly.
  663.         
  664. \******************************************************************/
  665.  
  666. ConfigureSaver()
  667. {
  668. int                itemType, itemHit, dialogDone = false, screenDepth;
  669. int                wormTally=15, wormLength=15, multiColored=0, wormLinks=0;
  670. Rect            itemRect;
  671. Handle            itemHandle;
  672. StringHandle    tempHandle;
  673. Str255            tempString;
  674. long            temp;
  675. DialogPtr        dialog;
  676. intH            modifier;
  677. SysEnvRec        theWorld;
  678. Boolean            colorQD;
  679. GDHandle        maxDevice, oldDevice;
  680. GrafPtr            viewScreen;
  681. CGrafPtr        workArea;
  682. CGrafPort        workPort;
  683.     
  684. SetPort( FrontWindow() );
  685. dialog = GetNewDialog( cBaseResID, cNilPointer, cMoveToFront );
  686. CenterDialog( dialog );
  687.     
  688. if ( SysEnvirons( 1, &theWorld ) == envNotPresent )
  689.     colorQD = false;
  690. else
  691.     colorQD = (theWorld.hasColorQD);
  692.  
  693. if ( colorQD )
  694.     {
  695.     GetWMgrPort( &viewScreen );
  696.     maxDevice = GetMaxDevice( &(viewScreen->portRect) );
  697.     oldDevice = GetGDevice();
  698.     SetGDevice( maxDevice );
  699.     workArea = &workPort;
  700.     OpenCPort( workArea );
  701.     screenDepth = (**(*workArea).portPixMap).pixelSize;
  702.     SetGDevice( oldDevice );
  703.     CloseCPort( workArea );
  704.     }
  705.  
  706. modifier = (intH)GetResource( 'mods', cTallyNum );
  707. temp = (**modifier);
  708. if ( temp >= 1 AND temp < cMax-1 )
  709.     wormTally = temp;
  710. modifier = (intH)GetResource( 'mods', cLengthNum );
  711. temp = (**modifier);
  712. if ( temp >= 1 AND temp < cMax-1 )
  713.     wormLength = temp;
  714. modifier = (intH)GetResource( 'mods', cMultiNum );
  715. temp = (**modifier);
  716. if ( temp >= 0 AND temp <= 1 )
  717.     multiColored = temp;
  718. modifier = (intH)GetResource( 'mods', cLinksNum );
  719. temp = (**modifier);
  720. if ( temp >= 0 AND temp <= 1 )
  721.     wormLinks = temp;
  722.     
  723. GetDItem( dialog, cWormLength, &itemType, &itemHandle, &itemRect );
  724. SelIText( dialog, cWormLength, 0, 32767 );
  725. NumToString( (long)wormLength, tempString );
  726. SetIText( itemHandle, tempString );
  727. SelIText( dialog, cWormLength, 0, 0);
  728. GetDItem( dialog, cwormTally, &itemType, &itemHandle, &itemRect );
  729. SelIText( dialog, cwormTally, 0, 32767 );
  730. NumToString( (long)wormTally, tempString );
  731. SetIText( itemHandle, tempString );
  732. SelIText( dialog, cwormTally, 0, 32767 );
  733. GetDItem( dialog, cMultiColored, &itemType, &itemHandle, &itemRect );
  734. SetCtlValue( itemHandle, multiColored );
  735. if ( !colorQD OR screenDepth == 1 )
  736.     {
  737.     SetCtlValue( itemHandle, false );
  738.     HiliteControl( (ControlHandle)itemHandle, 255 );
  739.     }
  740. GetDItem( dialog, cWormLinks, &itemType, &itemHandle, &itemRect );
  741. SetCtlValue( itemHandle, wormLinks );
  742.  
  743. /* The following assigns DrawDefaultItem to the cUserItem dialog Item */
  744.  
  745. GetDItem( dialog, cUserItem, &itemType, &itemHandle, &itemRect );
  746. SetDItem( dialog, cUserItem, itemType, (Handle)&DrawDefaultItem, &itemRect);
  747. ShowWindow( dialog );
  748.     
  749. while ( dialogDone == false )
  750.     {
  751.     ModalDialog( cNilPointer, &itemHit );
  752.     switch ( itemHit )
  753.         {
  754.         case cMultiColored:
  755.             GetDItem( dialog, cMultiColored, &itemType, &itemHandle, &itemRect );
  756.             temp = GetCtlValue( itemHandle ); 
  757.             SetCtlValue( itemHandle, !temp );
  758.             break;
  759.             
  760.         case cWormLinks:
  761.             GetDItem( dialog, cWormLinks, &itemType, &itemHandle, &itemRect );
  762.             temp = GetCtlValue( itemHandle );
  763.             SetCtlValue( itemHandle, !temp );
  764.             break;
  765.                 
  766.         case cOK:
  767.             HideWindow( dialog );
  768.             dialogDone = true;
  769.             GetDItem( dialog, cwormTally, &itemType, &itemHandle, &itemRect );
  770.             GetIText( itemHandle, &tempString );
  771.             StringToNum( tempString, &temp );
  772.             if ( temp >= 1 AND temp < cMax-1 ) 
  773.                 wormTally = temp;
  774.             if ( temp > cMax-2 )
  775.                 wormTally = cMax-2;
  776.             if ( temp < 1 )
  777.                 wormTally = 1;
  778.             GetDItem( dialog, cWormLength, &itemType, &itemHandle, &itemRect );
  779.             GetIText( itemHandle, &tempString );
  780.             StringToNum( tempString, &temp );
  781.             if ( temp >= 1 AND temp < cMax-1 ) 
  782.                 wormLength = temp;
  783.             if ( temp > cMax-2 )
  784.                 wormLength = cMax-2;
  785.             if ( temp < 1 )
  786.                 wormLength = 1;
  787.             GetDItem( dialog, cMultiColored, &itemType, &itemHandle, &itemRect );
  788.             multiColored = GetCtlValue( itemHandle );
  789.             GetDItem( dialog, cWormLinks, &itemType, &itemHandle, &itemRect );
  790.             wormLinks = GetCtlValue( itemHandle );
  791.     
  792.             modifier = (intH)GetResource( 'mods', cTallyNum );
  793.             (**modifier) = wormTally;
  794.             ChangedResource( (Handle)modifier );
  795.             WriteResource( (Handle)modifier );
  796.             modifier = (intH)GetResource( 'mods', cLengthNum );
  797.             (**modifier) = wormLength;
  798.             ChangedResource( (Handle)modifier );
  799.             WriteResource( (Handle)modifier );
  800.             modifier = (intH)GetResource( 'mods', cMultiNum );
  801.             (**modifier) = multiColored;
  802.             ChangedResource( (Handle)modifier );
  803.             WriteResource( (Handle)modifier );
  804.             modifier = (intH)GetResource( 'mods', cLinksNum );
  805.             (**modifier) = wormLinks;
  806.             ChangedResource( (Handle)modifier );
  807.             WriteResource( (Handle)modifier );
  808.  
  809.             break;
  810.             
  811.         case cCancel:
  812.             HideWindow( dialog );
  813.             dialogDone = true;
  814.             break;
  815.         }
  816.     }
  817.     DisposDialog( dialog );
  818.  
  819. }/* ConfigureSaver */
  820.  
  821.  
  822. /************************* CenterDialog ***************************\
  823.  
  824.     CenterDialog() is a routine which will center a given dialog 
  825.     over the main screen.  
  826.     
  827.     To use it, pass CenterDialog() a pointer to your dialog.
  828.         
  829. \******************************************************************/
  830.  
  831. CenterDialog (adialog)
  832. DialogPtr adialog;
  833. {
  834.     int        xOffset, yOffset, dwidth, dheight;
  835.     Rect     arect;
  836.     
  837.     arect = (*adialog).portRect;
  838.     dwidth = arect.right - arect.left;
  839.     dheight = arect.bottom - arect.top;
  840.         
  841.     xOffset=((screenBits.bounds.right-screenBits.bounds.left)-dwidth) / 2;
  842.     yOffset=((screenBits.bounds.bottom -screenBits.bounds.top)-dheight) / 2;
  843.  
  844.     MoveWindow(adialog, xOffset, yOffset, false);
  845.     
  846. }/* CenterDialog */
  847.     
  848.  
  849. /************************** MoveWorms *****************************\
  850.  
  851.     MoveWorms() moves each worm a single "link".  Calling it 
  852.     once each time we receive the RUN message provides for our
  853.     single frame of animation per RUN message.  
  854.     
  855.     It starts by calculating where the next link will be placed.  
  856.     The incentive[] values are used to turn the worm around when 
  857.     it hits one of the edges of the screen.  The worm is given 
  858.     an "incentive" to move in the opposite direction.  The correct 
  859.     color for the worm is then determined and the link is drawn.
  860.         
  861. \******************************************************************/
  862.  
  863. MoveWorms()
  864. {
  865. int     h, v, ii;
  866. long i;
  867.     
  868.  
  869. /* Calculate the new position for each worm. */
  870.  
  871. for ( ii=0; ii<=gWormTally-1; ii++ )
  872.     {
  873.  
  874.     v = gV[ii];
  875.     h = gH[ii];
  876.         
  877.     if ( v == gMoveMax OR v == -(gMoveMax) )
  878.         {
  879.         i = Randomize(2);
  880.         if ( gIncentive[ii].h > 0 )
  881.             {
  882.             i = 1;
  883.             gIncentive[ii].h -= 1;
  884.             }
  885.         else if ( gIncentive[ii].h < 0 )
  886.             {
  887.             i = 0;
  888.             gIncentive[ii].h += 1;
  889.             }
  890.         switch (i)
  891.             {
  892.             case cNegative:
  893.                 h -= 1;
  894.                 if ( h < -(gMoveMax) )
  895.                     h = -(gMoveMax);
  896.                 break;
  897.                     
  898.             case cPositive:
  899.                 h += 1;
  900.                 if ( h > (gMoveMax) )
  901.                     h = (gMoveMax);
  902.                 break;
  903.             }
  904.         }
  905.     if ( h == gMoveMax OR h == -(gMoveMax) )
  906.         {
  907.         i = Randomize(2);
  908.         if ( gIncentive[ii].v > 0 )
  909.             {
  910.             i = 1;
  911.             gIncentive[ii].v -= 1;
  912.             }
  913.         else if ( gIncentive[ii].v < 0 )
  914.             {
  915.             i = 0;
  916.             gIncentive[ii].v += 1;
  917.             }
  918.         switch (i)
  919.             {
  920.             case cNegative:
  921.                 v -= 1;
  922.                 if ( v < -(gMoveMax) )
  923.                     v = -(gMoveMax);
  924.                 break;
  925.                     
  926.             case cPositive:
  927.                 v += 1;
  928.                 if ( v > (gMoveMax) )
  929.                     v = (gMoveMax);
  930.                 break;
  931.             }
  932.         }
  933.  
  934.     gWormRect[ii] = gWormTrail[ii][0];
  935.     OffsetRect( &gWormRect[ii], h, v );
  936.  
  937.  
  938. /* 
  939.     Now determine if the worm has hit one of the sides.  If it has, back it 
  940.     off and give it "incentive" to move in the other direction. 
  941. */
  942.  
  943.     if ( gWormRect[ii].left > gScreenRect.right+4 )
  944.         {
  945.         OffsetRect( &gWormRect[ii], -h, 0 );
  946.         gIncentive[ii].h = -10;
  947.         }
  948.     if ( gWormRect[ii].right < gScreenRect.left-4 )
  949.         {
  950.         OffsetRect( &gWormRect[ii], -h, 0 );
  951.         gIncentive[ii].h = 10;
  952.         }
  953.     if ( gWormRect[ii].bottom < gScreenRect.top-4 )
  954.         {
  955.         OffsetRect( &gWormRect[ii], 0, -v );
  956.         gIncentive[ii].v = 10;
  957.         }
  958.     if ( gWormRect[ii].top > gScreenRect.bottom+4 )
  959.         {
  960.         OffsetRect( &gWormRect[ii], 0, -v );
  961.         gIncentive[ii].v = -10;
  962.         }
  963.         
  964.     gV[ii] = v;
  965.     gH[ii] = h;
  966.     
  967.  
  968. /* Determine the correct color for the worm and draw the new link. */
  969.  
  970.     if ( gMultiColored AND gColorQD AND gScreenDepth > 3 )
  971.         CalcRGBColor();
  972.     ForeColor(whiteColor);
  973.     if ( gScreenDepth > 3 AND gColorQD )
  974.         {
  975.         if ( !gMultiColored )
  976.             RGBForeColor( &gSpawnColor[ii] );
  977.         if ( gMultiColored )
  978.             RGBForeColor( &gWormColor );
  979.         }
  980.     if ( gWormLinks )
  981.         FrameOval( &gWormRect[ii] );
  982.     else
  983.         PaintOval( &gWormRect[ii] );
  984.     }
  985.     
  986. } /* MoveWorms */
  987.  
  988.  
  989. /************************ CalcRGBColor ****************************\
  990.  
  991.     CalcRGBColor() is used to calculate a random RGB color for the
  992.     worms.  RGB colors range from 0 to about 65000 for each part
  993.     of the color (R, G, B).  We call Randomize() twice for each 
  994.     part of the RGBColor getting a random number between 0 and 
  995.     30000 each time and adding them together.  We must do this 
  996.     twice since Randomize() will only accept values up to 32768.
  997.     We end up with a value from 0-60000.  We then add 5000
  998.     to each to assure that the random RGBColor we return is 
  999.     bright enough to be visible.
  1000.  
  1001.     After this, we do another random check and adjust the values 
  1002.     of one or two parts of the RGBColor down to 1000.  What this 
  1003.     does is to make brighter, more colorful worms.  Without it, the
  1004.     worms are a kind of dull pastel.
  1005.         
  1006. \******************************************************************/
  1007.  
  1008. CalcRGBColor()
  1009. {
  1010.     int i;
  1011.     
  1012.     i = Randomize( 30000 );
  1013.     gWormColor.red = i;
  1014.     i = Randomize( 30000 );
  1015.     gWormColor.red += (i+5000);
  1016.     i = Randomize( 30000 );
  1017.     gWormColor.green = i;
  1018.     i = Randomize( 30000 );
  1019.     gWormColor.green += (i+5000);
  1020.     i = Randomize( 30000 );
  1021.     gWormColor.blue = i;
  1022.     i = Randomize( 30000 );
  1023.     gWormColor.blue += (i+5000);
  1024.     
  1025.     i = Randomize(6);
  1026.     if ( i==0 )
  1027.         gWormColor.red = 1000;
  1028.     if ( i==1 )
  1029.         gWormColor.green = 1000;
  1030.     if ( i==2 )
  1031.         gWormColor.blue = 1000;
  1032.     if ( i==3 )
  1033.         {
  1034.         gWormColor.red = 1000;
  1035.         gWormColor.green = 1000;
  1036.         }
  1037.     if ( i==4 )
  1038.         {
  1039.         gWormColor.red = 1000;
  1040.         gWormColor.blue = 1000;
  1041.         }
  1042.     if ( i==5 )
  1043.         {
  1044.         gWormColor.blue = 1000;
  1045.         gWormColor.green = 1000;
  1046.         }
  1047.  
  1048. }/* CalcRGBColor */
  1049.  
  1050.  
  1051. /************************** Randomize *****************************\
  1052.  
  1053.     Randomize() takes a number from 0-32768 and returns a random 
  1054.     number within the range given.
  1055.         
  1056. \******************************************************************/
  1057.  
  1058. Randomize( range )
  1059. int        range;
  1060.  
  1061. {
  1062.     long rawResult;
  1063.  
  1064.     rawResult = Random();
  1065.     if ( rawResult < 0 )
  1066.         rawResult *= -1;
  1067.  
  1068.     return( (rawResult * range) / 32768 );
  1069.  
  1070. }/* Randomize */
  1071.  
  1072.  
  1073. /***************************** Wait *******************************\
  1074.  
  1075.     This procedure is not actually used by WormMucking.  It is 
  1076.     provided here for possible assistance with other modules you 
  1077.     may write.  In some of the modules, it is desirable to have 
  1078.     the animation appear at a specified speed.  Sunset provides 
  1079.     for this in general by timing the calls to modules once every 
  1080.     60th of a second.  If you need to slow down your own animation, 
  1081.     or want a piece of animation to run the same speed on different 
  1082.     machines, this routine can come in handy.  To use it, set up
  1083.     these #defines at the beginning of the module:
  1084.  
  1085.         #define cSeconds                60
  1086.         #define cTicks                  1
  1087.         
  1088.     Then when you wish to delay the animation, make a call to 
  1089.     this routine.  A call might look like:
  1090.     
  1091.         Wait( 30, cTicks );      ....or....
  1092.         Wait( 3, cSeconds );     ...etc....
  1093.         
  1094.     A "tick" is 1/60th of a second.  An example of a module 
  1095.     that uses this routine is TGMBA.  After the 
  1096.     lightning is displayed on the screen, this routine is 
  1097.     called to delay erasing the screen for approximately 
  1098.     1/2 second, or 30 ticks.
  1099.         
  1100. \******************************************************************/
  1101.  
  1102. Wait(length, unit)
  1103. int     length, unit;
  1104. {
  1105.     long    ticks, timer;
  1106.  
  1107.     length *= unit;
  1108.     ticks = TickCount();
  1109.     timer = ticks+length;
  1110.     while ( timer > ticks )
  1111.         ticks = TickCount();
  1112.                 
  1113. }/* Wait */
  1114.  
  1115.  
  1116.  
  1117.  
  1118.  
  1119.  
  1120. /*
  1121.     This is the end of the source code to Tutorial.c.  Below is a
  1122.     description of the resources in Tutorial.╣.rsrc and then a 
  1123.     discussion of the animation techniques used by several of the
  1124.     Sunset modules.
  1125. */
  1126.  
  1127.  
  1128.  
  1129.  
  1130.  
  1131.  
  1132. /************************* Tutorial.╣.rsrc ************************\
  1133.  
  1134.     The Tutorial.╣.rsrc file contains five types of resources.
  1135.     
  1136.     Actually, a resource file is not absolutely necessary in 
  1137.     order to make a screen saver.  The dialog box for the customize
  1138.     options can be created from within the screen saver itself.
  1139.     Most of the other resources, such as PICTs and vers, are only
  1140.     used to add completeness to the saver - not because they have
  1141.     to be there.  Working with a resource file is a bit easier 
  1142.     however and also adds more flexibility.
  1143.     
  1144.     The five types of resources in this resource file are:
  1145.     
  1146.     DITL -     A Dialog ITem List for all the items that appear 
  1147.             within the customize dialog box.  All of the radio
  1148.             buttons, checkboxes, pictures, etc, go in here.
  1149.             
  1150.     DLOG -    A Dialog box that references the above DITL.  This
  1151.             is the dialog box that is called and opened in the
  1152.             CustomizeSaver() routine.
  1153.             
  1154.     mods -    mods is a custom resource type used to hold the 
  1155.             users requests from CustomizeSaver().  There are 
  1156.             four within this resource file.  It is through these 
  1157.             resources that the saver communicates from the 
  1158.             ConfigureSaver() routine to the rest of the saver
  1159.             the user's wishes regarding modifying what the saver 
  1160.             does.  The four mods resources in this file are:
  1161.             
  1162.             WormTally:       Number of worms to draw.
  1163.             WormLength:      Length of worms.
  1164.             MultiColored: Whether each link of the worm is a 
  1165.                           different color.
  1166.             WormLinks:    Whether to draw solid sections of
  1167.                           worms or "links".
  1168.                           
  1169.     PICT -    The PICTs used in the WormMucking saver are only 
  1170.             used within the customize dialog box to give it a
  1171.             better look.  Some of the other savers included
  1172.             with Sunset contain PICTs that are used for 
  1173.             animation within the saver.  For example, Space 
  1174.             Duel contains PICTs of two ships that are animated.
  1175.             Another common type of resource used for animation 
  1176.             is the ICON resource.
  1177.             
  1178.     vers -    There are two vers resources usually included.
  1179.             They are used to display information about the 
  1180.             saver in the Get Info box from the Finder. One is
  1181.             used to display the actual version number of the
  1182.             saver.  The other is used to display the 
  1183.             copyright.
  1184.         
  1185. \******************************************************************/
  1186.  
  1187.  
  1188.  
  1189. /********************** Animation Techniques **********************\
  1190.  
  1191.     The main thing to remember in animating is to try to always 
  1192.     keep what you're animating on the screen.  If you have 
  1193.     a choice (you won't always have one), it is better to 
  1194.     Draw New/Erase Old than to Erase Old/Draw New.  The 
  1195.     reason is that whenever the object of your animation 
  1196.     is off the screen, no matter how short the period, the 
  1197.     object will flicker.  Sometimes this doesn't matter, 
  1198.     or cannot be avoided.
  1199.  
  1200.     There are many ways of doing animation.  The best method 
  1201.     to use can vary depending on the circumstances.  Below 
  1202.     is a brief discussion of several of the modules, what 
  1203.     animation techniques they used, and why.
  1204.     
  1205.     WormMucking:  The WormMucking module uses the Draw New/Erase 
  1206.     Old technique for its animation.  For each frame of 
  1207.     animation, the module goes through and moves each worm one 
  1208.     link.  It first calculates the new position for the next link 
  1209.     in the worm.  After drawing this link, it erases the last 
  1210.     link in the worm.  Then it shifts the logical location of each 
  1211.     link to reflect their new position in the worm "chain".  During 
  1212.     each frame of animation, this process is done once for each 
  1213.     worm.  This is pretty much a classic use of the Draw New/Erase 
  1214.     Old method of animation.  Looking at the worms move, you'll 
  1215.     notice that each link is separate from the link immediately 
  1216.     before it and doesn't overlap at all.  This is one of the 
  1217.     reasons the Draw New/Erase Old method is used.  If the new 
  1218.     link overlapped the previous link, then when the previous link 
  1219.     was erased, it would also erase part of the new link.  (This 
  1220.     condition is why the Billiards module uses the Erase Old/Draw 
  1221.     New method of drawing.)  So one of the main conditions of the
  1222.     Draw New/Erase Old method is (usually) that the new object not 
  1223.     overlap the old one.
  1224.     
  1225.     (While the new link doesn't overlap the link immediately 
  1226.     before it, it can overlap other links or other worms.  As 
  1227.     it works out though, the worms go fast enough so that this 
  1228.     effect is hardly noticeable.  Would it be worth it to make 
  1229.     the worms either not overlap at all or not erase part of a 
  1230.     "good" link?  There exists a version of WormMucking that 
  1231.     does just that.  However, keeping track of all that 
  1232.     information slows the module down to a crawl.  So the 
  1233.     tradeoff in this case is to let a little overlap/erase 
  1234.     happen for the sake of speed and multiple worms.)
  1235.     
  1236. \******************************************************************/
  1237.